# -*- coding: utf-8 -*-
# @Time : 2024/8/6 16:31
# @Author : sdk007
#此版本采用了线程的方式来播放声音
import time

from HCNetSDK import *
from paho.mqtt import client as mqtt_client
import json
import random
from threading import Thread

from playsound import playsound

class devClass(object):
    def __init__(self):
        self.hikSDK = self.LoadSDK()  # 加载sdk库
        self.iUserID = -1  # 登录句柄
        self.alarmHandle = -1  # 布防句柄
        self.listenHandle = -1  # 监听句柄
        self.basePath = ''  # 基础路径
        self.msg_callback_func = MSGCallBack_V31(self.g_fMessageCallBack_Alarm)  # 注册回调函数实现
        self.status = 0  #比对成功标志位
        #self.beepstatus = 0 #蜂鸣器是否运行结束状态标志位
        #self.status2 = 0 #蜂鸣器是否需要开启状态标志位
        self.broker = '127.0.0.1'  # 因为服务器在本机上运行，这个地址代表本机，
        self.port = 1883
        self.topic = "/python/mqtt"  # 主题
        self.client_id = ''
        self.client = ''

#mqtt connect
    def connect_mqtt(self):
        client_id = f'python-mqtt-{random.randint(0, 1000)}'  # 随机生成客户端id

        def on_connect(client, userdata, flags, rc, properties):
            if rc == 0:
                print("Connected to MQTT Broker!")
            else:
                print("Failed to connect, return code %d\n", rc)

        # Set Connecting Client ID
        client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2, client_id)  # 创建客户端
        client.on_connect = on_connect
        client.connect(self.broker, self.port)
        return client
   
    def beed(self,threadName,time):
        
        count = 0 
        while count<time:
            playsound("/home/book/Desktop/beep2.mp3") #file contiune time :2s
            count+=1
        #self.beepstatus = 1 #蜂鸣器是否运行结束状态标志位

        



    def LoadSDK(self):
        hikSDK = None
        try:
            print("netsdkdllpath: ", netsdkdllpath)
            hikSDK = load_library(netsdkdllpath)
        except OSError as e:
            print('动态库加载失败', e)
        return hikSDK

    # 报警信息回调函数实现代码
    def g_fMessageCallBack_Alarm(self, lCommand, pAlarmer, pAlarmInfo, dwBufLen, pUser):
        """
        解析报警信息
        """
        pAlarmer = pAlarmer.contents
        sDeviceIP = str(pAlarmer.sDeviceIP, encoding="gbk").rstrip('\x00')
        sSerialNumber = str(pAlarmer.sSerialNumber, encoding="gbk").rstrip('\x00')
        print(f"lCommand: {hex(lCommand)}, lUserID: {pAlarmer.lUserID}, sDeviceIP: {sDeviceIP}, sSerialNumber: {sSerialNumber}")
        self.struFaceSnap = cast(pAlarmInfo,
                                 LPNET_VCA_FACESNAP_RESULT).contents

        #人脸抓拍报警信息
        if lCommand == ALARM_LCOMMAND_ENUM.COMM_UPLOAD_FACESNAP_RESULT.value:


            # 事件时间
            dwYear = (self.struFaceSnap.dwAbsTime >> 26) + 2000
            dwMonth = (self.struFaceSnap.dwAbsTime >> 22) & 15
            dwDay = (self.struFaceSnap.dwAbsTime >> 17) & 31
            dwHour = (self.struFaceSnap.dwAbsTime >> 12) & 31
            dwMinute = (self.struFaceSnap.dwAbsTime >> 6) & 63
            dwSecond = (self.struFaceSnap.dwAbsTime >> 0) & 63
            strAbsTime = f"{dwYear}_{dwMonth}_{dwDay}_{dwHour}_{dwMinute}_{dwSecond}"

            # 人脸属性信息
            sFaceAlarmInfo = "Abs时间:" + strAbsTime + ",是否戴口罩：" + str(
                self.struFaceSnap.struFeature.byMask) + ",是否微笑：" + str(
                self.struFaceSnap.struFeature.bySmile)
            print(sFaceAlarmInfo)
            struFaceSnapAlarm = self.struFaceSnap
            """
            print(f"Face Snap alarm BrokenNetHttp[{struFaceSnapAlarm.byBrokenNetHttp}]Abs[{strAbsTime}] \
                     FaceID[{struFaceSnapAlarm.dwFacePicID}] Smart[{struFaceSnapAlarm.bySmart}] byGroup[{struFaceSnapAlarm.struFeature.byAgeGroup}] Sex[{struFaceSnapAlarm.struFeature.bySex}] EyeGlass[{struFaceSnapAlarm.struFeature.byEyeGlass}] \
                       StayDuration[{struFaceSnapAlarm.fStayDuration}] FaceScore[{struFaceSnapAlarm.dwFaceScore}] AlarmEndMark[{struFaceSnapAlarm.byAlarmEndMark}] RepeatTimes[{struFaceSnapAlarm.byRepeatTimes}]-StorageIP[{struFaceSnapAlarm.sStorageIP}] Storage Port[{struFaceSnapAlarm.wStoragePort}],TargeInfo :IvmsChannel[{struFaceSnapAlarm.struDevInfo.byIvmsChannel}]IvmsChannelEx[{struFaceSnapAlarm.wDevInfoIvmsChannelEx}] \
                       , Deviation[{struFaceSnapAlarm.struFeature.byAgeDeviation}] FacePicQuality[{struFaceSnapAlarm.byFacePicQuality}] UIDLen[{struFaceSnapAlarm.byUIDLen}] Mask[{struFaceSnapAlarm.struFeature.byMask}] Smile[{struFaceSnapAlarm.struFeature.bySmile}] \
                       TargeInfo: ID[{struFaceSnapAlarm.struTargetInfo.dwID}] X[{struFaceSnapAlarm.struTargetInfo.struRect.fX}] Y[{struFaceSnapAlarm.struTargetInfo.struRect.fY}] Height[{struFaceSnapAlarm.struTargetInfo.struRect.fHeight}] Width[{struFaceSnapAlarm.struTargetInfo.struRect.fWidth}] \
                       Rect:X[{struFaceSnapAlarm.struRect.fX}] Y[{struFaceSnapAlarm.struRect.fY}] Height[{struFaceSnapAlarm.struRect.fHeight}] Width[{struFaceSnapAlarm.struRect.fWidth}] \
                       AddInfo[{struFaceSnapAlarm.byAddInfo}]  \
                       LivenessDetectionStatus[{struFaceSnapAlarm.byLivenessDetectionStatus}] \
                       FaceExpression[{struFaceSnapAlarm.struFeature.byFaceExpression}] byRes1[{struFaceSnapAlarm.struFeature.byRes1}] Hat[{struFaceSnapAlarm.struFeature.byHat}] ")
            """


            # 指定保存图片的完整路径（包括文件名）

            if self.struFaceSnap.dwFacePicLen > 0:
                self.buff11 = bytes(self.struFaceSnap.pBuffer1[
                               :self.struFaceSnap.dwFacePicLen])  # [:x]属于切片操作，在pbuff1中提取包含x前的数据,dwFacePicLen为图片数据长度

            if self.struFaceSnap.dwBackgroundPicLen > 0:
                self.buff22 = bytes(self.struFaceSnap.pBuffer2[:self.struFaceSnap.dwBackgroundPicLen])


        elif lCommand == ALARM_LCOMMAND_ENUM.COMM_SNAP_MATCH_ALARM.value:

            print("str = ", self.struFaceSnap.dwFacePicLen)
            print("*********************************************************")
            print("对比数据")
            p_cont_device = cast(pAlarmInfo, POINTER(NET_VCA_FACESNAP_MATCH_ALARM))
            contents = p_cont_device.contents

            print("相似度: ", contents.fSimilarity)
            print("dwsize:", contents.dwSize)
            print("byMatchPicNum:", contents.byMatchPicNum)

            print("设备识别抓拍图片指针 : ", contents.pSnapPicBuffer)
            print("设备识别抓拍图片长度: ", contents.dwSnapPicLen)
            print("图片数据传输方式: ", contents.byPicTransType)

            # 事件时间
            dwYear = (contents.struSnapInfo.dwAbsTime >> 26) + 2000
            dwMonth = (contents.struSnapInfo.dwAbsTime >> 22) & 15
            dwDay = (contents.struSnapInfo.dwAbsTime >> 17) & 31
            dwHour = (contents.struSnapInfo.dwAbsTime >> 12) & 31
            dwMinute = (contents.struSnapInfo.dwAbsTime >> 6) & 63
            dwSecond = (contents.struSnapInfo.dwAbsTime >> 0) & 63
            strAbsTime = f"{dwYear}_{dwMonth}_{dwDay}_{dwHour}_{dwMinute}_{dwSecond}"
            print("contents.struSnapInfo.dwAbsTime: ", contents.struSnapInfo.dwAbsTime)
            # 人脸抓拍
            print("绝对时标: ", strAbsTime)
            print("性别:", contents.struSnapInfo.bySex)
            print("是否带眼镜:", contents.struSnapInfo.byGlasses)
            print("年龄段 : ", contents.struSnapInfo.byAgeGroup)
            print("抓拍人脸子图的长度 : ", contents.struSnapInfo.dwSnapFacePicLen)
            print("抓拍人脸子图的图片数据:", contents.struSnapInfo.pBuffer1)
            print("sIpV4:", contents.struSnapInfo.struDevInfo.struDevIP.sIpV4)

            # 对比
            print("非授权名单人脸子图的长度:", contents.struBlockListInfo.dwBlockListPicLen)
            print("非授权名单人脸子图的图片数据: ", contents.struBlockListInfo.pBuffer1)
            print("结构体大小: ", contents.struBlockListInfo.struBlockListInfo.dwSize)
            print("非授权名单等级: ", contents.struBlockListInfo.struBlockListInfo.byLevel)
            print("性别: ", contents.struBlockListInfo.struBlockListInfo.struAttribute.bySex)
            str_value = string_at(contents.struBlockListInfo.struBlockListInfo.struAttribute.byName).decode('gbk')
            
            print("姓名:  ", str_value)
            if contents.fSimilarity > 0.6:
                self.status = 1
                #self.status2 = 1 #蜂鸣器是否需要开启状态标志位

                

            if self.status == 1:
                self.status = 0
                #if dev.status2 == 1:
                #使用try防止线程重复start
                try:
                    thread1 = Thread(target=dev.beed, args=("sound",5))  # create threading
                    thread1.start()
                except RuntimeError as e:
                    print("Thread already started or cannot start again -", e)
                #create json data format
                msg = {
                    "ip":sDeviceIP,
                    "DevSerialNumber":sSerialNumber,
                    "Sex":contents.struSnapInfo.bySex,
                    "AgeGroup":contents.struSnapInfo.byAgeGroup,
                    "Similarity":contents.fSimilarity,
                    "byGlasses":contents.struSnapInfo.byGlasses,
                    "ContrastStatus": contents.byContrastStatus
                }
		
                json_data = json.dumps(msg, indent=4) #
                #json_data = json.dumps(msg2, indent=4).encode('utf-8')
                                
                result = self.client.publish(self.topic, json_data)
                
                status2 = result[0]
                if status2 == 0:
                    print(f"Send `{msg}` to topic `{self.topic}`")
                else:
                    print(f"Failed to send message to topic {self.topic}")
                from datetime import datetime
                # 获取当前时间戳
                timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
                # 创建文件名

                filename1 = f'image_{timestamp}_1.jpg'
                filename2 = f'image_{timestamp}_2.jpg'
                with open(f'./pic/{filename1}', 'wb') as fp1:
                    fp1.write(self.buff11)
                with open(f'./pic/{filename2}', 'wb') as fp1:
                    fp1.write(self.buff22)
               
            print("*********************************************************")
        # ISAPI协议报警信息
        elif lCommand == ALARM_LCOMMAND_ENUM.COMM_ISAPI_ALARM.value:
            print('ISAPI协议报警信息上传')
            Alarm_struct = cast(pAlarmInfo, LPNET_DVR_ALARM_ISAPI_INFO).contents

            print(
                f'byDataType: {Alarm_struct.byDataType}, '
                f'byPicturesNumber: {Alarm_struct.byPicturesNumber}, '
            )

            # 报警信息XML或者JSON数据
            DataLen = Alarm_struct.dwAlarmDataLen
            if DataLen != 0:
                from datetime import datetime
                # 获取当前时间的datetime对象
                current_time = datetime.now()
                timestamp_str = current_time.strftime('%Y%m%d_%H%M%S')

                buffInfo = string_at(Alarm_struct.pAlarmData, DataLen)
                with open(f'./pic/ISAPIInfo_{timestamp_str}.txt', 'wb') as fpInfo:
                    fpInfo.write(buffInfo)

            #报警信息图片数据
            pNum = Alarm_struct.byPicturesNumber
            if pNum > 0:
                print('报警图片数量：', pNum)
                STRUCT = NET_DVR_ALARM_ISAPI_PICDATA * pNum
                PicStruct = cast(Alarm_struct.pPicPackData, POINTER(STRUCT)).contents
                for nPicIndex in range(pNum):
                    nSize = PicStruct[nPicIndex].dwPicLen
                    if nSize != 0:
                        from datetime import datetime
                        # 获取当前时间的datetime对象
                        current_time = datetime.now()
                        timestamp_str = current_time.strftime('%Y%m%d_%H%M%S')

                        buffInfo = string_at(PicStruct[nPicIndex].pPicData, nSize)
                        # strName = str(PicStruct[nPicIndex].szFilename)
                        sFileName = (f'./pic/ISAPI_Pic[{nPicIndex}]_{timestamp_str}.jpg')
                        with open(sFileName, 'wb') as fpPic:
                            fpPic.write(buffInfo)
        elif lCommand == ALARM_LCOMMAND_ENUM.COMM_ALARM_RULE.value:
            strudection = cast(pAlarmInfo,LPNET_VCA_RULE_ALARM).contents
            print("行为事件类型扩展%d" % strudection.struRuleInfo.wEventTypeEx)

            if strudection.struRuleInfo.wEventTypeEx == HIGH_DENSITY:
                print("检测到人员聚集")
                print("密度%f" %strudection.struRuleInfo.uEventParam.struHighDensity.fDensity)
                from datetime import datetime
                # 获取当前时间戳
                timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
                # 创建文件名
                filename1 = f'image_{timestamp}_1.jpg'
                buff1 = bytes(strudection.pImage [:strudection.dwPicDataLen])
                with open(f'./pic/{filename1}', 'wb') as fp1:
                    fp1.write(buff1)
            elif strudection.struRuleInfo.wEventTypeEx == EVENT_LOITER:
                print("人员徘徊")
                # 获取当前时间戳
                print("人员徘徊时间%d" % strudection.struRuleInfo.uEventParam.struLoiter.wDuration)
                timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
                # 创建文件名
                filename1 = f'image_{timestamp}_1.jpg'
                buff1 = bytes(strudection.pImage[:strudection.dwPicDataLen])
                with open(f'./pic/{filename1}', 'wb') as fp1:
                    fp1.write(buff1)
            elif strudection.struRuleInfo.wEventTypeEx == EVENT_TRAVERSE_PLANE:
                print("检测到人员越界侦测")
                print("越界方向%" % trudection.struRuleInfo.uEventParam.struLoiter.struTraversePlane.dwCrossDirection)
                # 获取当前时间戳
                timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
                # 创建文件名
                filename1 = f'image_{timestamp}_1.jpg'
                buff1 = bytes(strudection.pImage[:strudection.dwPicDataLen])
                with open(f'./pic/{filename1}', 'wb') as fp1:
                    fp1.write(buff1)

		
        return True

    # 设置SDK初始化依赖库路径
    def SetSDKInitCfg(self):
        # 设置HCNetSDKCom组件库和SSL库加载路径
        if sys_platform == 'windows':
            strPath = os.getcwd().encode('gbk') + b'\lib'
            sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
            sdk_ComPath.sPath = strPath
            print('strPath: ', strPath)
            if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_SDK_PATH.value,
                                                 byref(sdk_ComPath)):
                print('NET_DVR_SetSDKInitCfg: 2 Succ')
            if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_LIBEAY_PATH.value,
                                                 create_string_buffer(strPath + b'\libcrypto-1_1-x64.dll')):
                print('NET_DVR_SetSDKInitCfg: 3 Succ')
            if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_SSLEAY_PATH.value,
                                                 create_string_buffer(strPath + b'\libssl-1_1-x64.dll')):
                print('NET_DVR_SetSDKInitCfg: 4 Succ')
        else:
            strPath = os.getcwd().encode('utf-8') + b'\lib'
            sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
            sdk_ComPath.sPath = strPath
            if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_SDK_PATH.value,
                                                 byref(sdk_ComPath)):
                print('NET_DVR_SetSDKInitCfg: 2 Succ')
            if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_LIBEAY_PATH.value,
                                                 create_string_buffer(strPath + b'/libcrypto.so.1.1')):
                print('NET_DVR_SetSDKInitCfg: 3 Succ')
            if self.hikSDK.NET_DVR_SetSDKInitCfg(NET_SDK_INIT_CFG_TYPE.NET_SDK_INIT_CFG_SSLEAY_PATH.value,
                                                 create_string_buffer(strPath + b'/libssl.so.1.1')):
                print('NET_DVR_SetSDKInitCfg: 4 Succ')
        self.basePath = strPath

    # 通用设置，日志/回调事件类型等
    def GeneralSetting(self):

        # 日志的等级（默认为0）：0-表示关闭日志，1-表示只输出ERROR错误日志，2-输出ERROR错误信息和DEBUG调试信息，3-输出ERROR错误信息、DEBUG调试信息和INFO普通信息等所有信息
        self.hikSDK.NET_DVR_SetLogToFile(3, b'./SdkLog_Python/', False)

        # 通用参数配置
        sdkCfg = NET_DVR_LOCAL_GENERAL_CFG()
        sdkCfg.byAlarmJsonPictureSeparate = 1  # 控制JSON透传报警数据和图片是否分离，0-不分离，1-分离（分离后走COMM_ISAPI_ALARM回调返回）
        self.hikSDK.NET_DVR_SetSDKLocalCfg(NET_SDK_LOCAL_CFG_TYPE.NET_DVR_LOCAL_CFG_TYPE_GENERAL.value, byref(sdkCfg))

        # 设置报警回调函数
        self.hikSDK.NET_DVR_SetDVRMessageCallBack_V31(self.msg_callback_func, None)

    def LoginDev(self, ip, username, pwd):
        # 登录参数，包括设备地址、登录用户、密码等
        struLoginInfo = NET_DVR_USER_LOGIN_INFO()
        struLoginInfo.bUseAsynLogin = 0  # 同步登录方式
        struLoginInfo.sDeviceAddress = ip  # 设备IP地址
        struLoginInfo.wPort = 8000  # 设备服务端口
        struLoginInfo.sUserName = username  # 设备登录用户名
        struLoginInfo.sPassword = pwd  # 设备登录密码
        struLoginInfo.byLoginMode = 0

        # 设备信息, 输出参数
        struDeviceInfoV40 = NET_DVR_DEVICEINFO_V40()

        self.iUserID = self.hikSDK.NET_DVR_Login_V40(byref(struLoginInfo), byref(struDeviceInfoV40))
        if self.iUserID < 0:
            print("Login failed, error code: %d" % self.hikSDK.NET_DVR_GetLastError())
            self.hikSDK.NET_DVR_Cleanup()
        else:
            print('登录成功，设备序列号：%s' % str(struDeviceInfoV40.struDeviceV30.sSerialNumber, encoding="utf8").rstrip('\x00'))

    def SetupAlarm(self):
        # 启用布防
        struAlarmParam = NET_DVR_SETUPALARM_PARAM()
        struAlarmParam.dwSize = sizeof(struAlarmParam)
        struAlarmParam.byAlarmInfoType = 1  # 智能交通报警信息上传类型：0- 老报警信息（NET_DVR_PLATE_RESULT），1- 新报警信息(NET_ITS_PLATE_RESULT)
        struAlarmParam.byDeployType = 1  # 布防类型：0-客户端布防，1-实时布防
        self.alarmHandle = self.hikSDK.NET_DVR_SetupAlarmChan_V41(self.iUserID, byref(struAlarmParam))
        if self.alarmHandle < 0:
            print("NET_DVR_SetupAlarmChan_V41 失败, error code: %d" % self.hikSDK.NET_DVR_GetLastError())
            self.hikSDK.NET_DVR_Logout(self.iUserID)
            self.hikSDK.NET_DVR_Cleanup()
        else:
            print('NET_DVR_SetupAlarmChan_V41 Succ! ')

    def StartListen(self, listenIp, listenPort):
        # 启动报警监听并且设置回调函数接收事件
        self.listenHandle = self.hikSDK.NET_DVR_StartListen_V30(listenIp, listenPort, self.msg_callback_func, None)
        print('self.listenHandle: ', self.listenHandle)
        if self.listenHandle < 0:
            print("NET_DVR_StartListen_V30失败, error code: %d" % self.hikSDK.NET_DVR_GetLastError())
            self.hikSDK.NET_DVR_Cleanup()
            exit()

    def StopListen(self):
        if self.listenHandle > -1:
            # 停止监听
            self.hikSDK.NET_DVR_StopListen_V30(self.listenHandle)

    def StopAlarm(self):
        if self.alarmHandle > -1:
            # 撤销布防，退出程序时调用
            self.hikSDK.NET_DVR_CloseAlarmChan_V30(self.alarmHandle)

    def LogoutDev(self):
        if self.iUserID > -1:
            # 撤销布防，退出程序时调用
            self.hikSDK.NET_DVR_Logout(self.iUserID)


if __name__ == '__main__':
    dev = devClass()
    dev.SetSDKInitCfg()  # 设置SDK初始化依赖库路径
    dev.hikSDK.NET_DVR_Init()  # 初始化sdk
    dev.GeneralSetting()  # 通用设置，日志，回调函数等
    dev.client = dev.connect_mqtt()
    dev.client.loop_start()  # 循环开始
    #thread1 = Thread(target=dev.beed, args=("sound",3))

    '''
    说明：这里包含布防和监听两种方式，其中，布防方式接收事件需要先布防，监听方式不需要布防，两种方式二选一即可。
    '''
    dev.LoginDev(ip=b'192.168.1.64', username=b"admin", pwd=b"gac13579")  # 登录设备
    dev.SetupAlarm()
    # dev.StartListen(listenIp=b'10.184.68.129', listenPort=7200)

            

    
    while True:
        user_input = input("请输入一个字符 (输入 Y 退出): ").strip().upper()
        if user_input == 'Y':
            break
    # dev.StopListen()
    dev.StopAlarm()  # 停止布防
    dev.LogoutDev()  # 登出设备
    dev.hikSDK.NET_DVR_Cleanup()  # 反初始化sdk
    
    
    
    
    
